<?php

/**
 * Организует и контролирует доступ к модулям
 */
class CMSController implements iCMSController {
	
	private
		$aSystemModules      = array ('system','users'),
		$adminMode           = false,
		$modulesObjects      = array (),
		$currentModuleName   = '',
		$currentModuleMethod = '__default';
	
	private static $link = null;

	private static $pageTitle = '';

	private static $instance = false;

	/**
	 * Возвращает объект класса CMSController
	 * @return CMSController
	 */
	public static function getInstance () {
		if (self::$instance === false) {
			self::$instance = new CMSController;
		}
		return self::$instance;
	}

	private function __construct () {}

	public function __clone () {
		throw new CoreException (lang('error_controller_clone',__CLASS__));
	}
	
	/**
	 * Возвращает ссылку текущей страницы
	 * @return string
	 */
	public static function getLink () {
		if (self::$link === null) {
			self::$link = HTTP_HOST . '/' . trim(getRequest ('u'), '/');
			
			$forceFormat = getRequest ('force_format');
			if ($forceFormat != '') {
				self::$link .= '.' . $forceFormat;
			}
			
			$requestURI = GET2String ($_GET);
			if ($requestURI != '') {
				self::$link .= '?' . $requestURI;
			}
		}
		
		return self::$link;
	}
	
	/**
	 * Производит анализ и выполнение действий, переданных в URI
	 * @return void
	 */
	final public function GSpot ()	{
		$parser = PageParser::getInstance ();

		$url = getRequest ('u');
		$url = trim ($url, '/');
		$urlParts = explode ('/', $url);
		$cntParts = count ($urlParts);

		$n = 0;
		
		// Проходимся по УРЛу
		for ($i=0; $i < $cntParts; ++$i) {
			if ($urlParts[$i] == '') {
				continue;
			}
			
			if ($urlParts[$i] == 'admin' && $i == 0) { // Признак того, что мы в админпанели
				$this->adminMode = true;
				continue;
			}
			
			if ($i == 0 || ($i == 1 && $this->adminMode)) {
				$this->currentModuleName = $urlParts[$i];
				if (isset ($urlParts[$i+1])) { // Следующий должен быть названием метода
					$this->currentModuleMethod = $urlParts[$i+1];
					++$i; // Перескакиваем через название метода
				}
				continue;
			}

			// Если никакое условие не подходит, эмитируем request-параметр
			$_REQUEST['param'.$n] = $urlParts[$i];
			++$n;
		}
		
		$mainPage = false;
		
		if ($this->currentModuleName == '') {
			$this->currentModuleName = Regedit::getInstance()->get ('//settings/site/default_module');
			
			$mainPage = true;
			
			if ($this->currentModuleName == '') {
				throw new ControllerException (lang('error_empty_module_name',__CLASS__));
			}
		}
		
		$oMainResponse = $this->runModuleByMethod ();
		
		// Заголовок страницы
		if (!$mainPage && self::$pageTitle == '') {
			self::$pageTitle = lang('module_header', $this->currentModuleName);
		}
				
		// Устанавливаем заголовок, ключевые слова и описание страницы
		if (($siteTitle = Regedit::getInstance()->get('//settings/site/title')) != '') {
			$mainPage ? self::$pageTitle = $siteTitle : self::$pageTitle .= ' — ' . $siteTitle;
		}

		// Выводим контент		
		$parser->render ($oMainResponse);
	}
	
	/**
	 * Устанавливает заголовок страницы
	 * @param string $title
	 * @return void
	 */
	public static function setPageTitle ($title) {
		self::$pageTitle = $title;
	}
	
	/**
	 * Возвращает заголовок страницы
	 * @return string
	 */
	public static function getPageTitle () {
		return self::$pageTitle;
	}

	/**
	 * Подгружет класс модуля и создает его объект, который сохраняется в $this->modules
	 * @param string $moduleName имя модуля (должно совпадать с именем его класса)
	 * @return void
	 */
	private function loadModule ($moduleName) {
		if ($this->isModuleLoaded ($moduleName)) {
			return;
		}
		$file = ENGINE_ROOT . '/modules/' . $moduleName . '/' . ($this->isAdminMode() ? 'admin' : 'class') . '.php';
		if (!file_exists ($file)) {
			throw new NotFound;
		}

		include $file;
		
		$premissionsFile = ENGINE_ROOT . '/modules/' . $moduleName . '/permissions.php';
		$aMethodsPremissions = array ();
		
		if (file_exists ($premissionsFile)) {
			include $premissionsFile;
			if (isset ($PERMISSIONS) && is_array ($PERMISSIONS)	) {
				$aMethodsPremissions = $PERMISSIONS;
				unset ($PERMISSIONS);
			}
		}

		$this->modules[$moduleName] = array ();
		$this->modules[$moduleName]['object'] = new $moduleName;
		$this->modules[$moduleName]['premissions'] = $aMethodsPremissions;
		
		loadLang (ENGINE_ROOT . '/modules/' . $moduleName . '/i18n.' . LANG . '.php', $moduleName);
	}
	
	/**
	 * Возвращает объект модуля
	 * @param string $moduleName имя модуля
	 * @return object
	 */
	public function getModule ($moduleName) {
		if (isset ($this->modules[$moduleName])) {
			return $this->modules[$moduleName]['object'];
		}
		
		$this->loadModule ($moduleName);
		
		return $this->modules[$moduleName]['object'];
	}

	/**
	 * Запускает метод модуля и возвращает его результат в виде объекта ModuleResponse
	 * @param string $moduleName имя модуля
	 * @param string $method имя метода
	 * @param array $aParams массив параметров
	 * @param boolean $isMacros указатель на то, что медот вызван макросом из шаблона
	 * @return ModuleResponse
	 */
	final public function runModuleMethod ($moduleName, $method, $aParams = false, $isMacros = false) {
		try {
			if (!$this->isModuleLoaded ($moduleName)) {
				$this->loadModule ($moduleName);
			}
	
			if (!$aParams || !$cntParams = count ($aParams)) {
				$sParams = '';
			} else {
				$sParams = '';
				
				for ($i = 0; $i < $cntParams; ++$i) {
					$sParams .= '$aParams[' . $i . ']';
					if ($i != $cntParams - 1) {
						$sParams .= ', ';
					}
				}
			}

			if (!$this->modules[$moduleName]['object']->isMethodExists ($method)) {
				throw new NotFound;
			}
			
			$realModuleName = $this->modules[$moduleName]['object']->getRealClassName ($method);
			$oRefMethod = new ReflectionMethod ($realModuleName, $method);
			if (!$oRefMethod->isPublic() || $oRefMethod->isStatic()) {
				throw new NotFound;
			}
			
			// Проверка на доступность выполнения юзверем данного действия
			$this->accessModule ($this->modules[$moduleName]['premissions'], $moduleName, $method, $isMacros);
			
			/*if ($realModuleName == $moduleName) {
				eval ('$oResponse = $this->modules[\'' . $moduleName . '\'][\'object\']->' . $method . ' (' . $sParams . ');');
			} else {
				$oResponse = $this->modules[$moduleName]['object']->__call ($method, $aParams);
			}*/
			
			//$oResponse = $this->modules[$moduleName]['object']->execute ($method, $sParams);
			eval ('$oResponse = $this->modules[\'' . $moduleName . '\'][\'object\']->' . $method . ' (' . $sParams . ');');
			
			if (!($oResponse instanceof ModuleResponse)) {
				throw new ControllerException (lang('error_controller_module_return_invalid_object',__CLASS__));
			}
			
			return $oResponse;
		}
		catch (ControllerException $e) {
			$response = new ModuleResponse;
			$response->setTemplate ($isMacros ? 'macros_error' : 'index');
			$response->addObject (new SystemError ($e->getMessage ()));
			return $response;
		}
		catch (AccessException $e) {
			header("HTTP/1.1 403 Forbidden");			
			$response = new ModuleResponse;
			$response->setTemplate ($isMacros ? 'macros_error' : 'index');
			$response->addObject (new SystemError ($e->getMessage ()));
			return $response;
		}
		catch (NotFound $e) {
			header('Status: 404 Not Found', true, 404);
			header('HTTP/1.0 404 Not Found', true, 404);
			$response = new ModuleResponse;
			$response->setTemplate ('404');
			return $response;
		}
	}

	/**
	 * Проверяет, был загружен модуль или нет
	 * @param string $moduleName имя модуля
	 * @return boolean
	 */
	private function isModuleLoaded ($moduleName) {
		return isset ($this->modules[$moduleName]) ? true : false;
	}

	/**
	 * Возвращает true если мы в панели администратора
	 * @return boolean
	 */
	final public function isAdminMode () {
		return $this->adminMode ? true : false;
	}

	/**
	 * Возвращает название текущего модуля
	 * @return string
	 */
	final public function getCurrentModuleName () {
		return $this->currentModuleName;
	}
	
	/**
	 * Возвращает название текущего метода
	 * @return string
	 */
	final public function getCurrentMethod () {
		return $this->currentModuleMethod;
	}

	/**
	 * Запуск модуля по методу
	 * @return ModuleResponse
	 */
	private function runModuleByMethod () {
		if ($this->currentModuleName == '') {
			throw new ControllerException (lang('error_controller_module_not_detected',__CLASS__));
		}
		if ($this->currentModuleMethod == '') {
			throw new ControllerException (lang('error_controller_module_default_method_not_detected',__CLASS__));
		}

		return $this->runModuleMethod ($this->currentModuleName, $this->currentModuleMethod);
	}

	/**
	 * Проверка прав доступа кмодулю
	 * @param array $premissions
	 * @param string $sModule
	 * @param string $sMethod
	 * @param boolean $isMacros
	 * @return void
	 */
	private function accessModule (array $premissions, $sModule, $sMethod, $isMacros) {
		$oUser = ExplaySession::getUser ();
		
		// Админка только админам
		if ($this->adminMode && userGroup ($oUser->getValue ('group')) != 'admin') {
			throw new AccessException (lang('error_method_access_denied',__CLASS__) . $sModule . '::' . $sMethod);
		}
		
		// Если метода нет в правах, то разрешаем доступ всем
		if (!isset ($premissions[$sMethod])) {
			return;
		}
		
		// Запрещаем внешний доступ к макросу
		if (!$isMacros && isset ($premissions[$sMethod]['macros']) && $premissions[$sMethod]['macros']) {
			throw new AccessException (lang('error_method_access_denied',__CLASS__));
		}
		
		if (isset ($premissions[$sMethod]['group']) && !in_array (userGroup ($oUser->getValue ('group')), $premissions[$sMethod]['group'])) {
			throw new AccessException (lang('error_method_access_denied',__CLASS__) . $sModule . '::' . $sMethod);
		}
	}

}